package ws

import (
	"encoding/json"
	"errors"
	"goadmin/dao"
	"goadmin/model"
	"log"
	"time"

	"github.com/gorilla/websocket"
)

type Sub struct {
	userId uint
	online bool
}

var subChan = make(chan Sub)
var subs = make(map[uint]uint)

func sendMsg(msg *model.Msg) error {
	if conn, ok := conns[msg.FromId]; ok {
		msg.CreatedAt = time.Now()
		jsonBody, err := json.Marshal(msg)
		if err != nil {
			log.Println(err)
			return err
		}
		conn.WriteMessage(websocket.TextMessage, jsonBody)
		return nil
	} else {
		return errors.New("user not online")
	}
}

func subProc() {
	for {
		sub, ok := <-subChan
		if !ok {
			continue
		}
		for userId, targetId := range subs {
			if targetId == sub.userId {
				sendMsg(&model.Msg{
					Body:   model.SubscribeMsg{Online: sub.online},
					FromId: userId,
					ToId:   targetId,
					Type:   model.MsgTypeSubscribe,
				})
			}
		}
	}
}

func updateConversation(msg model.Msg, isToOnline bool) {
	if msg.FromId == 0 || msg.ToId == 0 || msg.FromId == msg.ToId {
		return
	}
	conversation1, err := dao.GetOrCreateSingleConversation(msg.FromId, msg.ToId)
	if err != nil {
		log.Println(err)
		return
	}
	conversation1.LatestMsg = msg
	dao.UpdateConversationLastMsg(conversation1, msg, true)
	conversation2, err := dao.GetOrCreateSingleConversation(msg.ToId, msg.FromId)
	if err != nil {
		log.Println(err)
		return
	}
	conversation2.LatestMsg = msg
	dao.UpdateConversationLastMsg(conversation2, msg, isToOnline)
}

func hub(msg model.Msg) {
	switch msg.Type {
	case model.MsgTypeHistory:
		if historyBody, ok := msg.Body.(*model.HistoryMsg); ok {
			msgs, err := dao.GetMsgListByConversation(msg.FromId, msg.ToId, historyBody.Deadline, historyBody.Count)
			if err != nil {
				log.Printf("get history msg failed: %v\n", err)
				return
			}
			sendMsg(&model.Msg{
				Body:   model.HistoryMsg{Count: int64(len(msgs)), Deadline: historyBody.Deadline, MsgList: msgs},
				FromId: msg.FromId,
				ToId:   msg.FromId,
				Type:   model.MsgTypeHistory,
			})
		}
		return
	case model.MsgTypeSubscribe:
		// 发生订阅事件时清空未读消息
		dao.ClearConversationUnread(msg.FromId, msg.ToId)
		subs[msg.FromId] = msg.ToId
		online := false
		if _, ok := conns[msg.ToId]; ok {
			online = true
		}
		sendMsg(&model.Msg{
			Body:   model.SubscribeMsg{Online: online},
			FromId: msg.FromId,
			ToId:   msg.ToId,
			Type:   model.MsgTypeSubscribe,
		})
		return
	case model.MsgTypeSystem:
		if systemBody, ok := msg.Body.(*model.SystemMsg); ok {
			switch systemBody.EventCode {
			case model.EventCodeRemoveConversation:
				dao.RemoveConversation(msg.FromId, msg.ToId)
				return
			case model.EventCodeTopConversation:
				// 置顶会话
				dao.SetConversationTop(msg.FromId, msg.ToId, time.Now().Unix())
				return
			case model.EventCodeUnTopConversation:
				// 取消置顶会话
				dao.SetConversationTop(msg.FromId, msg.ToId, 0)
				return
			}

		}
	}

	// save to mongodb
	dao.InsertMsg(msg)

	// if toId online, send to toId
	conn, online := conns[msg.ToId]
	if online {
		jsonBody, err := json.Marshal(msg)
		if err != nil {
			log.Println(err)
		}
		conn.WriteMessage(websocket.TextMessage, []byte(jsonBody))
	}
	// 当接收方在线，且订阅了发送方时，认为消息已读
	isRecv := false
	if sub, ok := subs[msg.ToId]; ok {
		if sub == msg.FromId {
			isRecv = true
		}
	}
	updateConversation(msg, online && isRecv)
}

func commonProc(msgChan chan []byte) {
	for {
		msgJson, ok := <-msgChan
		if !ok {
			continue
		}
		msg := model.Msg{}
		if err := json.Unmarshal(msgJson, &msg); err != nil {
			log.Println(err)
			continue
		}
		msg.CreatedAt = time.Now()
		go hub(msg)
	}
}
